5. 쿠버네티스 개요4
3.5. 설정 항목과 볼륨
3.5.1. 컨피그맵(ConfigMap)과 시크릿(Secret)을 활용한 애플리케이션 설정 관리
설정 관리의 필요성:
쿠버네티스는 파드로 실행되는 애플리케이션과 실행 시 필요한 설정 항목을 독립적으로 관리하는 **컨피그맵(ConfigMap)**과 시크릿(Secret) 기능을 제공
- ConfigMap (컨피그맵): 애플리케이션 설정 정보를 키-값 쌍으로 저장하는 쿠버네티스 리소스. 환경 변수나 설정 파일로 주입 가능
- Secret (시크릿): 비밀번호, API 키 등 민감한 정보를 저장하는 쿠버네티스 리소스. Base64로 인코딩되어 저장
- 하드코딩 (Hardcoding): 설정 값을 소스 코드에 직접 작성하는 방식. 변경 시 코드 수정과 재빌드가 필요해 유연성이 떨어짐
설정 관리 방식 비교:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[하드코딩 방식 (비권장)]
애플리케이션 코드 내부에 설정 직접 작성
│
문제점:
├─ 환경별로 이미지 재빌드 필요
├─ 설정 변경 시 코드 수정 필요
└─ 민감 정보(비밀번호) 코드에 노출
vs
[쿠버네티스 설정 관리]
애플리케이션과 설정 분리
│
├─ ConfigMap (일반 설정)
│ └─ 데이터베이스 URL, 포트, 로그 레벨 등
│
└─ Secret (민감 정보)
└─ 비밀번호, API 키, 인증서 등
장점:
├─ 동일 이미지로 여러 환경 배포 가능
├─ 설정만 변경하여 재배포 (빌드 불필요)
└─ 민감 정보 안전하게 관리
컨피그맵과 시크릿의 차이:
ConfigMap vs Secret:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[ConfigMap]
용도: 일반 설정 정보
│
├─ 평문(plaintext) 저장
├─ 애플리케이션 설정
├─ 데이터베이스 URL
└─ 로그 레벨 설정
vs
[Secret]
용도: 민감한 보안 정보
│
├─ Base64 인코딩 저장
├─ 데이터베이스 비밀번호
├─ API 키
└─ TLS 인증서
주의: Secret은 암호화가 아닌 인코딩
(etcd에 암호화 저장하려면 별도 설정 필요)
- 평문 (Plaintext): 암호화되지 않은 일반 텍스트. 누구나 읽을 수 있는 상태
- Base64 인코딩: 이진 데이터를 ASCII 문자열로 변환하는 방식. 암호화가 아니므로 쉽게 디코딩 가능 (보안 목적 X)
- etcd: 쿠버네티스가 모든 클러스터 상태를 저장하는 분산 키-값 저장소. ConfigMap, Secret 데이터도 여기 저장
사용 방법:
컨피그맵과 시크릿은 정보를 키(key)와 값(value)의 쌍으로 정의. 컨테이너에서는 이런 정보를 다음과 같은 방법으로 사용:
사용 방식:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[방식 1: 환경 변수로 사용]
ConfigMap/Secret → 컨테이너 환경 변수
│
예시:
FOO=$FOO (FOO 환경 변수로 접근)
[방식 2: 파일로 마운트]
ConfigMap/Secret → 읽기 전용 볼륨 → 파일
│
예시:
/mnt/config/database.conf (파일로 접근)
- 환경 변수 (Environment Variable): 운영체제나 프로세스가 참조하는 동적 값. 애플리케이션 설정을 코드 외부에서 주입하는 표준 방식
- 마운트 (Mount): 파일 시스템을 특정 경로에 연결하는 것. 볼륨을 컨테이너의 디렉터리에 연결하면 해당 경로로 접근 가능
- 읽기 전용 (Read-only): 데이터 읽기만 가능하고 수정이 불가능한 상태. Secret을 볼륨으로 마운트할 때 보안을 위해 주로 사용
ConfigMap과 Secret 예제
매니페스트 파일:
파일명: ubuntu-configmap-secret.yaml
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ConfigMap 정의 (일반 설정 정보)
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
apiVersion: v1 # Kubernetes API 버전 (v1은 ConfigMap, Secret 등 코어 리소스용)
kind: ConfigMap # 리소스 타입 - ConfigMap (일반 설정 데이터 저장)
metadata:
name: testconf # ConfigMap 이름
data: # 키-값 쌍으로 설정 데이터 정의
foo: foo-val # key: foo, value: foo-val (평문 저장)
app.config: | # 멀티라인 설정 파일도 저장 가능
server.port=8080
server.host=localhost
---
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Secret 정의 (민감한 보안 정보)
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
apiVersion: v1 # Kubernetes API 버전
kind: Secret # 리소스 타입 - Secret (민감 정보 저장)
metadata:
name: testsecret # Secret 이름
type: Opaque # Secret 타입 (Opaque는 일반 키-값 데이터용, 가장 흔히 사용)
data: # 키-값 쌍으로 보안 데이터 정의
bar: YmFyLXZhbA== # Base64 인코딩된 문자열 (원본: bar-val)
# Base64 인코딩 방법: echo -n "bar-val" | base64
---
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ConfigMap과 Secret을 사용하는 Pod 정의
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
apiVersion: v1 # Kubernetes API 버전
kind: Pod # 리소스 타입 - Pod
metadata:
name: test-pod # Pod 이름
spec:
containers:
- name: ubuntu # 컨테이너 이름
image: ubuntu:22.04 # Ubuntu 22.04 LTS 이미지
command: ["sh"] # 실행할 명령어
args:
- -euc # 옵션: -e (에러 시 종료), -u (미정의 변수 에러), -c (명령어 실행)
- | # YAML 멀티라인 문자열
for i in $(seq 1 10) ; do # 1부터 10까지 반복
echo "Value of foo is $FOO , bar is $(cat /mnt/bar/bar)" # 환경 변수 FOO와 파일 /mnt/bar/bar 출력
sleep 3 # 3초 대기
done ; sleep infinity # 10번 실행 후 무한 대기
# ConfigMap을 환경 변수로 사용
env:
- name: FOO # 컨테이너 내부 환경 변수 이름
valueFrom: # 값을 외부에서 가져옴
configMapKeyRef: # ConfigMap에서 참조
name: testconf # ConfigMap 이름
key: foo # ConfigMap의 키 (foo의 값인 "foo-val"을 가져옴)
# Secret을 파일로 마운트
volumeMounts:
- name: testsecret # 아래 volumes에서 정의한 볼륨 이름
readOnly: true # 읽기 전용 마운트 (Secret 수정 방지)
mountPath: /mnt/bar # 컨테이너 내부 마운트 경로
# Secret을 볼륨으로 정의
volumes:
- name: testsecret # 볼륨 이름 (volumeMounts의 name과 일치)
secret: # Secret을 볼륨으로 사용
secretName: testsecret # Secret 이름
# Secret의 각 키는 파일명으로, 값은 파일 내용으로 마운트됨
# 예: /mnt/bar/bar 파일에 "bar-val" 내용 저장
- apiVersion: 쿠버네티스 API의 버전. 리소스 타입에 따라 v1, apps/v1, storage.k8s.io/v1 등 다양한 버전 사용
- kind: 생성할 쿠버네티스 리소스의 타입. Pod, ConfigMap, Secret, Deployment 등
- metadata: 리소스의 메타 정보. 이름(name), 네임스페이스(namespace), 레이블(labels) 등 포함
- type: Opaque: Secret의 기본 타입. 임의의 키-값 데이터를 저장하는 범용 타입
- valueFrom: 환경 변수 값을 외부 리소스(ConfigMap, Secret)에서 참조할 때 사용하는 필드
- volumeMounts: 컨테이너 내부에서 볼륨을 특정 경로에 마운트할 때 사용하는 설정
동작 방식:
ConfigMap과 Secret 동작:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[ConfigMap: testconf]
data:
foo: "foo-val"
↓
[환경 변수로 주입]
컨테이너 내부:
$FOO = "foo-val"
↓
[애플리케이션에서 접근]
echo $FOO
→ "foo-val" 출력
vs
[Secret: testsecret]
data:
bar: "YmFyLXZhbA==" (Base64)
↓
[볼륨으로 마운트]
컨테이너 내부 파일시스템:
/mnt/bar/
└─ bar (파일명은 키 이름)
내용: "bar-val" (자동으로 디코딩됨)
↓
[애플리케이션에서 접근]
cat /mnt/bar/bar
→ "bar-val" 출력
배포 및 확인:
# ConfigMap, Secret, Pod 배포
kubectl apply -f ubuntu-configmap-secret.yaml
# Pod 상태 확인
kubectl get pods
# 로그 확인 (첫 1줄만 출력)
kubectl logs test-pod | head -n 1
출력 예시:
Value of foo is foo-val , bar is bar-val
ConfigMap의 foo 값이 환경 변수 $FOO로 사용되고, Secret의 bar 값도 파일 /mnt/bar/bar를 통해 주어지는 것을 확인할 수 있음
리소스 삭제:
kubectl delete -f ubuntu-configmap-secret.yaml
ConfigMap 생성 방법
명령어로 생성:
# 리터럴 값으로 ConfigMap 생성
kubectl create configmap app-config --from-literal=database.url=mysql://localhost:3306 --from-literal=log.level=INFO
# 파일에서 ConfigMap 생성(예시)
kubectl create configmap nginx-config --from-file=nginx.conf
# 디렉터리의 모든 파일에서 ConfigMap 생성(예시)
kubectl create configmap app-configs --from-file=./config-files/
# ConfigMap 확인
kubectl get configmap app-config -o yaml
YAML 매니페스트로 생성:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
# 단일 키-값
database.url: "mysql://localhost:3306"
log.level: "INFO"
# 멀티라인 설정 파일
application.yaml: |
server:
port: 8080
host: 0.0.0.0
database:
driver: mysql
pool: 10
Secret 생성 방법
명령어로 생성:
# 리터럴 값으로 Secret 생성
kubectl create secret generic db-secret --from-literal=username=admin --from-literal=password=mysecretpassword
# 파일에서 Secret 생성
kubectl create secret generic tls-secret --from-file=tls.crt --from-file=tls.key
# Secret 확인 (Base64 인코딩된 상태로 출력)
kubectl get secret db-secret -o yaml
# Secret 디코딩 확인
kubectl get secret db-secret -o jsonpath='{.data.password}' | base64 -d
YAML 매니페스트로 생성:
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
# Base64 인코딩 필수
username: YWRtaW4= # admin
password: bXlzZWNyZXRwYXNzd29yZA== # mysecretpassword
# 또는 stringData 사용 (자동으로 Base64 인코딩)
# stringData:
# username: admin
# password: mysecretpassword
Base64 인코딩/디코딩:
# 인코딩 (Linux/macOS)
echo -n "mysecretpassword" | base64
# 디코딩 (Linux/macOS)
echo "bXlzZWNyZXRwYXNzd29yZA==" | base64 -d
# 인코딩 (Windows PowerShell)
[Convert]::ToBase64StringUTF8.GetBytes("mysecretpassword")
# 디코딩 (Windows PowerShell)
[Text.Encoding]::UTF8.GetStringFromBase64String("bXlzZWNyZXRwYXNzd29yZA==")
환경 변수로 전체 ConfigMap/Secret 가져오기
envFrom 사용:
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
containers:
- name: app
image: myapp:1.0
# ConfigMap의 모든 키-값을 환경 변수로 주입
envFrom:
- configMapRef:
name: app-config # ConfigMap의 모든 키가 환경 변수로 설정됨
- secretRef:
name: db-secret # Secret의 모든 키가 환경 변수로 설정됨
envFrom 동작:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[ConfigMap: app-config]
data:
DATABASE_URL: mysql://localhost
LOG_LEVEL: INFO
↓
[envFrom으로 주입]
컨테이너 환경 변수:
$DATABASE_URL = mysql://localhost
$LOG_LEVEL = INFO
(모든 키가 환경 변수명이 됨)
3.5.2. 볼륨을 사용한 스토리지 관리
볼륨의 필요성
컨테이너의 임시성:
컨테이너는 일시적인 실행 단위이므로, 컨테이너 내부 파일에 기록된 내용은 컨테이너 종료와 함께 폐기됨
- 볼륨 (Volume): 컨테이너에 연결되는 외부 저장 공간. 컨테이너 재시작이나 삭제 후에도 데이터 유지 가능
- 임시성 (Ephemeral): 일시적인 성질. 컨테이너는 기본적으로 임시적이며 종료 시 내부 데이터가 사라짐
- 영속성 (Persistence): 데이터가 영구적으로 유지되는 성질. 파드가 삭제되어도 볼륨의 데이터는 남아있음
컨테이너 데이터 생명주기:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[볼륨 없는 경우]
│
컨테이너 시작
↓
파일 쓰기 (logs, data, cache)
↓
컨테이너 종료 (크래시, 업데이트, 스케일 인)
↓
모든 데이터 소멸 ✗
vs
[볼륨 사용하는 경우]
│
컨테이너 시작 + 볼륨 마운트
↓
파일 쓰기 → 볼륨에 저장
↓
컨테이너 종료
↓
볼륨 데이터 유지 ✓
↓
새 컨테이너 시작 + 동일 볼륨 마운트
↓
이전 데이터 접근 가능
쿠버네티스 볼륨 타입:
쿠버네티스는 더 긴 수명을 제공하는 추가 데이터 저장 영역으로 다양한 종류의 볼륨 기능을 지원
쿠버네티스 볼륨 분류:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[퍼시스턴트 볼륨 (Persistent Volume)]
파드와 독립적인 수명
│
├─ 파드 삭제되어도 데이터 유지
├─ 다른 파드에서 재사용 가능
│
예시:
├─ PersistentVolume (PV)
├─ 클라우드 디스크 (AWS EBS, GCP PD)
└─ NFS, Ceph, iSCSI
vs
[임시 볼륨 (Ephemeral Volume)]
파드와 동일한 수명
│
├─ 파드 삭제 시 데이터도 삭제
├─ 임시 데이터 저장용
│
예시:
├─ emptyDir (노드 디스크)
├─ emptyDir (메모리)
└─ 일반 임시 볼륨 (동적 프로비저닝)
스토리지 플러그인:
스토리지 인터페이스:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[쿠버네티스 직접 관리]
├─ emptyDir
├─ hostPath
├─ configMap
└─ secret
[Container Storage Interface (CSI)]
표준화된 스토리지 플러그인 규격
│
├─ AWS EBS CSI Driver
├─ GCP PD CSI Driver
├─ Azure Disk CSI Driver
├─ Ceph CSI Driver
└─ NFS CSI Driver
- 퍼시스턴트 볼륨 (Persistent Volume, PV): 파드와 독립적인 수명을 가지는 영구 저장소. 클러스터 관리자가 프로비저닝
- 임시 볼륨 (Ephemeral Volume): 파드와 동일한 수명을 가지는 저장소. 파드 삭제 시 함께 삭제됨
- emptyDir: 파드가 노드에 할당될 때 생성되는 빈 디렉터리. 파드 내 컨테이너 간 데이터 공유에 사용
- hostPath: 노드의 파일 시스템을 파드에 마운트하는 방식. 개발/테스트 용도 (프로덕션 비권장)
- CSI (Container Storage Interface): 컨테이너 오케스트레이션 시스템과 스토리지 시스템 간의 표준 인터페이스
- NFS (Network File System): 네트워크를 통해 파일 시스템을 공유하는 프로토콜. 여러 노드에서 동시 접근 가능
클라우드 제공업체도 클러스터에 영구 디스크를 사용하기 위한 CSI 드라이버를 제공
3.5.3. 퍼시스턴트 볼륨 (PersistentVolume)
PV와 PVC 개념
PV (PersistentVolume):
PV를 사용하면 파드 수명과 별도로 독립적인 영구 데이터 저장 영역을 관리할 수 있음
PV와 PVC 관계:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[1. 스토리지 준비]
│
클라우드 디스크 생성 (AWS EBS, GCP PD 등)
또는 NFS 서버 준비
↓
[2. PersistentVolume (PV) 생성]
│
실제 스토리지를 쿠버네티스에 등록
│
spec:
capacity: 10Gi
accessModes: ReadWriteOnce
csi:
driver: ebs.csi.aws.com
volumeHandle: vol-abc123
↓
[3. PersistentVolumeClaim (PVC) 생성]
│
사용자의 스토리지 요청 정의
│
spec:
resources:
requests:
storage: 10Gi
accessModes: ReadWriteOnce
↓
[4. PVC와 PV 바인딩]
│
쿠버네티스가 조건에 맞는 PV를 PVC에 자동 할당
↓
[5. Pod에서 PVC 사용]
│
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvc
PV, PVC, Pod 관계:
스토리지 계층 구조:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[인프라 계층]
실제 스토리지
(AWS EBS, NFS 등)
↓
[쿠버네티스 추상화 계층]
PersistentVolume (PV)
(스토리지 리소스 표현)
↓
[사용자 요청 계층]
PersistentVolumeClaim (PVC)
(스토리지 사용 요청)
↓
[애플리케이션 계층]
Pod
(PVC를 볼륨으로 마운트)
- PV (PersistentVolume): 클러스터의 스토리지 리소스를 추상화한 객체. 실제 스토리지(EBS, NFS 등)와 연결됨
- PVC (PersistentVolumeClaim): 사용자가 스토리지를 요청하는 선언. 용량, 접근 모드 등을 명시하면 조건에 맞는 PV와 바인딩
- 바인딩 (Binding): PVC와 PV가 연결되는 과정. 쿠버네티스가 조건에 맞는 PV를 자동으로 찾아 연결
- 프로비저닝 (Provisioning): 스토리지를 생성하고 할당하는 과정. 수동(Static) 또는 자동(Dynamic)으로 수행
PV와 PVC 예제 (hostPath)
매니페스트 파일:
파일명: example-host-pv-pvc.yaml
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# PersistentVolume 정의
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
apiVersion: v1 # Kubernetes API 버전
kind: PersistentVolume # 리소스 타입 - PersistentVolume (영구 스토리지)
metadata:
name: example-pv # PV 이름
spec:
capacity: # 스토리지 용량
storage: 5Gi # 5기가바이트 제공
accessModes: # 접근 모드
- ReadWriteOnce # RWO: 단일 노드에서 읽기/쓰기 가능
storageClassName: manual # 스토리지 클래스 이름 (PVC와 일치해야 바인딩)
hostPath: # 호스트 경로 사용 (개발/테스트용, 프로덕션 비권장)
path: "/tmp" # 노드의 /tmp 디렉터리를 스토리지로 사용
# 주의: hostPath는 데이터 영속성이 보장되지 않음 (노드 재시작 시 /tmp 삭제 가능)
---
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# PersistentVolumeClaim 정의
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
apiVersion: v1 # Kubernetes API 버전
kind: PersistentVolumeClaim # 리소스 타입 - PersistentVolumeClaim (스토리지 요청)
metadata:
name: example-pvc # PVC 이름
spec:
accessModes: # 요청하는 접근 모드 (PV와 일치해야 함)
- ReadWriteOnce # RWO 요청
storageClassName: manual # PV의 storageClassName과 일치 (바인딩 조건)
resources: # 리소스 요청
requests:
storage: 5Gi # 5Gi 스토리지 요청 (PV의 capacity 이하여야 함)
AccessModes 설명:
AccessModes 종류:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[ReadWriteOnce (RWO)]
단일 노드에서 읽기/쓰기 가능
│
├─ 가장 일반적인 모드
├─ 동일 노드의 여러 파드 동시 접근 가능
└─ 다른 노드의 파드는 접근 불가
지원 스토리지:
AWS EBS, GCP PD, Azure Disk 등
[ReadOnlyMany (ROX)]
여러 노드에서 읽기 전용
│
├─ 공유 데이터 배포용
└─ 쓰기 불가
지원 스토리지:
NFS, CephFS 등
[ReadWriteMany (RWX)]
여러 노드에서 읽기/쓰기 가능
│
├─ 공유 스토리지 필요
└─ 가장 유연하지만 지원 제한적
지원 스토리지:
NFS, CephFS, GlusterFS 등
- AccessModes (접근 모드): 볼륨에 접근하는 방식을 정의. RWO, ROX, RWX 세 가지 모드 존재
- ReadWriteOnce (RWO): 단일 노드에서만 읽기/쓰기 가능. 대부분의 클라우드 블록 스토리지가 지원
- ReadOnlyMany (ROX): 여러 노드에서 읽기 전용 접근. 공유 설정 파일 배포 등에 사용
- ReadWriteMany (RWX): 여러 노드에서 동시 읽기/쓰기 가능. NFS 같은 공유 스토리지 필요
- storageClassName: PV와 PVC를 매칭하는 기준. 동일한 클래스끼리만 바인딩됨
- capacity: PV가 제공하는 스토리지 용량. Gi(기비바이트), Ti(테비바이트) 단위 사용
배포 및 확인:
# PV와 PVC 배포
kubectl apply -f example-host-pv-pvc.yaml
# PV 상태 확인
kubectl get pv example-pv
# PVC 상태 확인
kubectl get pvc example-pvc
출력 예시:
# PV 상태
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
example-pv 5Gi RWO Retain Bound default/example-pvc manual
# PVC 상태
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
example-pvc Bound example-pv 5Gi RWO manual
STATUS: Bound는 PVC가 PV에 성공적으로 바인딩되었음을 의미
PVC를 사용하는 Pod
매니페스트 파일:
파일명: ubuntu-example-pvc.yaml
apiVersion: v1 # Kubernetes API 버전
kind: Pod # 리소스 타입 - Pod
metadata:
name: test-pod # Pod 이름
spec:
containers:
- name: ubuntu # 컨테이너 이름
image: ubuntu:22.04 # Ubuntu 이미지
command: ["sh"] # 실행할 명령어
args:
- -euc # 옵션
- | # YAML 멀티라인 문자열
for i in $(seq 1 10) ; do # 1부터 10까지 반복
date >> /mnt/data # 타임스탬프를 /mnt/data에 추가
sleep 3 # 3초 대기
done ; sleep infinity # 10번 실행 후 무한 대기
volumeMounts: # 볼륨 마운트 정의
- mountPath: /mnt/ # 컨테이너 내부 마운트 경로
name: example-vol # 아래 volumes에서 정의한 볼륨 이름
volumes: # Pod에서 사용할 볼륨 정의
- name: example-vol # 볼륨 이름
persistentVolumeClaim: # PVC를 볼륨으로 사용
claimName: example-pvc # PVC 이름 (위에서 생성한 example-pvc 참조)
배포 및 테스트:
# Pod 배포
kubectl apply -f ubuntu-example-pvc.yaml
# Pod 상태 확인
kubectl get pod test-pod
# 볼륨에 기록된 데이터 확인 (처음 3줄만 출력)
kubectl exec -it test-pod -c ubuntu -- cat /mnt/data | head -n 3
출력 예시:
Sat Jan 14 12:00:00 UTC 2026
Sat Jan 14 12:00:03 UTC 2026
Sat Jan 14 12:00:06 UTC 2026
영속성 테스트:
파드와 PV 수명은 독립적이므로 파드를 삭제해도 PV는 삭제되지 않고, 파드 재생성 후에도 똑같은 PVC로 기존의 볼륨을 사용할 수 있음
# Pod 삭제
kubectl delete -f ubuntu-example-pvc.yaml
# Pod 재생성
kubectl apply -f ubuntu-example-pvc.yaml
# 이전 데이터가 유지되는지 확인 (처음 3줄은 이전 데이터)
kubectl exec -it test-pod -c ubuntu -- cat /mnt/data | head -n 3
영속성 테스트 결과:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[1차 실행]
Pod 생성 → 타임스탬프 10개 기록 → Pod 삭제
/mnt/data: 10줄 (12:00:00 ~ 12:00:27)
↓
[PV 유지]
Pod 삭제되어도 PV는 유지
데이터: 10줄 그대로 보존
↓
[2차 실행]
Pod 재생성 → 동일 PVC 마운트 → 타임스탬프 10개 추가
/mnt/data: 20줄 (이전 10줄 + 새로운 10줄)
↓
[결과]
영속성 확인 ✓
리소스 정리:
# Pod 삭제
kubectl delete -f ubuntu-example-pvc.yaml
# PVC와 PV 삭제
kubectl delete -f example-host-pv-pvc.yaml
3.5.4. 동적 프로비저닝 (Dynamic Provisioning)
StorageClass를 통한 자동 볼륨 생성
동적 프로비저닝이란?
PV를 수동으로 작성하지 않고, PVC 생성 시 자동으로 PV를 생성하는 기능
수동 vs 동적 프로비저닝:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[수동 프로비저닝 (Static)]
1. 관리자: 클라우드에서 디스크 생성
2. 관리자: PV 매니페스트 작성 및 배포
3. 사용자: PVC 생성
4. 시스템: PVC와 PV 바인딩
문제점:
├─ 관리자 개입 필요
├─ 수동 작업 많음
└─ 확장성 낮음
vs
[동적 프로비저닝 (Dynamic)]
1. 사용자: PVC 생성 (StorageClass 지정)
2. 시스템: 자동으로 디스크 생성 + PV 생성 + 바인딩
장점:
├─ 관리자 개입 불필요
├─ 자동화
└─ 확장성 높음
StorageClass (SC):
StorageClass 역할:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[StorageClass]
동적 프로비저닝 정책 정의
│
├─ provisioner: 어떤 CSI 드라이버 사용?
├─ parameters: 디스크 타입, IOPS, 암호화 등
└─ reclaimPolicy: 삭제 정책
↓
[PVC 생성 시]
StorageClass 참조
↓
[자동으로]
├─ 클라우드에 디스크 생성
└─ PV 생성 및 바인딩
- 동적 프로비저닝 (Dynamic Provisioning): PVC 생성 시 자동으로 스토리지와 PV를 생성하는 기능. 수동 관리 부담 감소
- 정적 프로비저닝 (Static Provisioning): 관리자가 미리 스토리지와 PV를 생성해두는 방식. 세밀한 제어 가능
- StorageClass (SC): 동적 프로비저닝 정책을 정의하는 리소스. 어떤 스토리지 드라이버와 설정을 사용할지 명시
- provisioner: StorageClass에서 볼륨을 프로비저닝할 CSI 드라이버 지정. 예: ebs.csi.aws.com
- reclaimPolicy: PVC 삭제 시 PV와 실제 스토리지 처리 방식. Delete(삭제), Retain(유지) 등
- volumeBindingMode: PV 바인딩 시점. Immediate(즉시) 또는 WaitForFirstConsumer(파드 스케줄링 후)
AWS EBS 동적 프로비저닝 예제 (예시, EKS 환경 필요)
EKS 클러스터의 StorageClass 확인:
# 기본 StorageClass 확인
kubectl get storageclass
# 상세 정보 확인
kubectl describe storageclass gp2
출력 예시:
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION
gp2 (default) kubernetes.io/aws-ebs Delete WaitForFirstConsumer true
AWS EBS StorageClass 정의(예시):
파일명: aws-ebs-storageclass.yaml
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# AWS EBS용 StorageClass 정의
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
apiVersion: storage.k8s.io/v1 # Storage API 버전
kind: StorageClass # 리소스 타입 - StorageClass
metadata:
name: fast-ssd # StorageClass 이름
provisioner: ebs.csi.aws.com # AWS EBS CSI 드라이버 사용
parameters: # EBS 볼륨 생성 파라미터
type: gp3 # EBS 볼륨 타입 (gp3: 최신 범용 SSD, gp2: 이전 버전, io1: 프로비저닝된 IOPS)
iops: "3000" # IOPS (gp3의 기본값: 3000, 최대 16000)
throughput: "125" # 처리량 MB/s (gp3의 기본값: 125, 최대 1000)
encrypted: "true" # 암호화 활성화
kmsKeyId: "arn:aws:kms:us-east-1:123456789012:key/abcd-1234" # KMS 키 (선택 사항)
volumeBindingMode: WaitForFirstConsumer # Pod 스케줄링 후 볼륨 생성 (가용 영역 맞춤)
reclaimPolicy: Delete # PVC 삭제 시 PV와 EBS 볼륨도 함께 삭제
allowVolumeExpansion: true # 볼륨 확장 허용 (용량 증가 가능)
AWS EBS 볼륨 타입:
AWS EBS 볼륨 타입 비교:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[gp3 (범용 SSD) - 권장]
기본 성능: 3000 IOPS, 125 MB/s
최대 성능: 16000 IOPS, 1000 MB/s
용도: 대부분의 워크로드
특징: 성능과 비용의 균형
[gp2 (이전 범용 SSD)]
성능: 볼륨 크기에 비례 (1GB당 3 IOPS)
용도: 레거시 워크로드
특징: gp3로 마이그레이션 권장
[io2 (프로비저닝된 IOPS SSD)]
최대 성능: 64000 IOPS, 1000 MB/s
용도: 고성능 데이터베이스
특징: 높은 IOPS 필요 시
[st1 (처리량 최적화 HDD)]
최대 성능: 500 IOPS, 500 MB/s
용도: 빅데이터, 로그 처리
특징: 순차 읽기/쓰기에 최적화
[sc1 (콜드 HDD)]
최대 성능: 250 IOPS, 250 MB/s
용도: 아카이브, 백업
특징: 가장 저렴
- AWS EBS (Elastic Block Store): AWS의 블록 스토리지 서비스. EC2 인스턴스에 연결하여 사용하는 가상 하드 디스크
- IOPS (Input/Output Operations Per Second): 초당 입출력 작업 수. 스토리지 성능 지표로, 높을수록 빠름
- 처리량 (Throughput): 단위 시간당 데이터 전송량. MB/s 단위로 측정
- gp3: 최신 범용 SSD 타입. IOPS와 처리량을 독립적으로 설정 가능하여 비용 효율적
- io2: 고성능 프로비저닝된 IOPS SSD. 데이터베이스 같은 고성능 워크로드용
- KMS (Key Management Service): AWS의 암호화 키 관리 서비스. EBS 볼륨 암호화에 사용
- allowVolumeExpansion: PVC 용량 증가 허용 여부. true면 볼륨 확장 가능
PVC 생성 (동적 프로비저닝):
파일명: aws-ebs-pvc.yaml
apiVersion: v1 # Kubernetes API 버전
kind: PersistentVolumeClaim # 리소스 타입 - PersistentVolumeClaim
metadata:
name: aws-ebs-claim # PVC 이름
spec:
accessModes:
- ReadWriteOnce # RWO 모드 (EBS는 RWO만 지원)
storageClassName: fast-ssd # 위에서 정의한 StorageClass 사용
resources:
requests:
storage: 20Gi # 20GB EBS 볼륨 자동 생성 요청
배포 및 확인:
# StorageClass 배포
kubectl apply -f aws-ebs-storageclass.yaml
# PVC 배포 (자동으로 EBS 볼륨과 PV 생성됨)
kubectl apply -f aws-ebs-pvc.yaml
# PVC 상태 확인
kubectl get pvc aws-ebs-claim
# 자동 생성된 PV 확인
kubectl get pv
출력 예시:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
aws-ebs-claim Bound pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890 20Gi RWO fast-ssd
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890 20Gi RWO Delete Bound default/aws-ebs-claim
Pod에서 사용:
파일명: pod-with-aws-ebs.yaml
apiVersion: v1
kind: Pod
metadata:
name: app-with-ebs
spec:
containers:
- name: app
image: nginx:1.25
volumeMounts:
- mountPath: /data # 컨테이너 내부 마운트 경로
name: ebs-storage # 볼륨 이름
volumes:
- name: ebs-storage
persistentVolumeClaim:
claimName: aws-ebs-claim # 동적으로 생성된 PVC 사용
# Pod 배포
kubectl apply -f pod-with-aws-ebs.yaml
# Pod 상태 확인
kubectl get pod app-with-ebs
# AWS 콘솔에서 EBS 볼륨 확인 가능
aws ec2 describe-volumes --filters "Name=tag:kubernetes.io/created-for/pvc/name,Values=aws-ebs-claim"
볼륨 확장 (allowVolumeExpansion: true):
# PVC 용량 증가 (20Gi → 50Gi)
kubectl patch pvc aws-ebs-claim -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'
# EBS 볼륨 자동 확장됨
kubectl get pvc aws-ebs-claim
GCP Persistent Disk 동적 프로비저닝 예제 (예시, GKE 환경 필요)
GKE 클러스터의 StorageClass:
파일명: gcp-pd-storageclass.yaml
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# GCP Persistent Disk용 StorageClass 정의
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd-gcp
provisioner: pd.csi.storage.gke.io # GCP PD CSI 드라이버
parameters:
type: pd-ssd # 디스크 타입 (pd-standard: HDD, pd-ssd: SSD, pd-balanced: 균형형 SSD)
replication-type: regional-pd # 복제 타입 (none: 단일 영역, regional-pd: 다중 영역)
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
allowVolumeExpansion: true
PVC 생성:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gcp-pd-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd-gcp
resources:
requests:
storage: 20Gi
3.5.5. 임시 볼륨 (Ephemeral Volume)
emptyDir 볼륨
emptyDir이란?
임시 볼륨(Ephemeral Volume)은 파드와 똑같은 수명을 가지며 파드가 종료되면 볼륨도 삭제됨. 주요 임시 볼륨으로는 emptyDir이 있음
emptyDir 특징:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[생명주기]
Pod 생성 → emptyDir 생성 (빈 디렉터리)
Pod 실행 중 → 데이터 저장 가능
Pod 삭제 → emptyDir 삭제 (데이터 소멸)
[저장 위치]
노드의 디스크 (기본값)
또는 메모리 (medium: Memory 설정 시)
[용도]
├─ 임시 데이터 캐싱
├─ 파드 내 컨테이너 간 데이터 공유
├─ 중간 처리 결과 저장
└─ 로그 파일 임시 보관
[주의]
영구 데이터 저장 불가
- emptyDir: 파드 생성 시 빈 디렉터리로 시작하는 임시 볼륨. 파드 내 컨테이너 간 데이터 공유에 주로 사용
- 캐싱 (Caching): 자주 사용하는 데이터를 임시 저장하여 접근 속도를 높이는 기법
- 사이드카 패턴: 메인 컨테이너를 보조하는 컨테이너를 함께 배치하는 패턴. emptyDir로 데이터 공유
- tmpfs: 메모리를 파일 시스템처럼 사용하는 기술. RAM에 저장되어 매우 빠르지만 휘발성
emptyDir 예제:
파일명: ubuntu-emptydir.yaml
apiVersion: v1 # Kubernetes API 버전
kind: Pod # 리소스 타입 - Pod
metadata:
name: test-pod # Pod 이름
spec:
containers:
# 컨테이너 1: emptyDir에 기록된 데이터를 읽는 컨테이너
- name: container-a # 컨테이너 이름
image: ubuntu:22.04 # Ubuntu 이미지
command: ["sh"] # 실행할 명령어
args:
- -euc # 옵션
- "tail -f /mnt/shared-file" # /mnt/shared-file을 실시간으로 읽기 (tail -f는 파일 끝에 추가되는 내용 출력)
volumeMounts: # 볼륨 마운트
- mountPath: /mnt/ # emptyDir을 /mnt/에 마운트
name: test-volume # 아래 volumes에서 정의한 볼륨 이름
# 컨테이너 2: emptyDir에 타임스탬프를 기록하는 컨테이너
- name: container-b # 컨테이너 이름
image: ubuntu:22.04 # Ubuntu 이미지
command: ["sh"] # 실행할 명령어
args:
- -euc # 옵션
- | # YAML 멀티라인 문자열
for i in $(seq 1 10) ; do # 1부터 10까지 반복
date >> /mnt/shared-file # 타임스탬프를 /mnt/shared-file에 추가
sleep 3 # 3초 대기
done ; sleep infinity # 10번 실행 후 무한 대기
volumeMounts: # 볼륨 마운트
- mountPath: /mnt/ # emptyDir을 /mnt/에 마운트
name: test-volume # 동일한 볼륨 이름 (컨테이너 간 공유)
volumes: # Pod에서 사용할 볼륨 정의
- name: test-volume # 볼륨 이름
emptyDir: # emptyDir 볼륨 사용
sizeLimit: 500Mi # 최대 용량 제한 (500MB, 초과 시 Pod Eviction)
# medium: Memory # 메모리에 저장 (기본값: 디스크)
동작 방식:
emptyDir 공유 동작:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[Worker Node]
│
/var/lib/kubelet/pods/<pod-uid>/volumes/kubernetes.io~empty-dir/test-volume/
│
├─ Pod 생성 시 자동으로 빈 디렉터리 생성
│
↓
[Container-B]
/mnt/shared-file
↓ (쓰기)
타임스탬프 기록
↓
[emptyDir (공유)]
shared-file 파일
↓ (읽기)
[Container-A]
/mnt/shared-file
↓
실시간 출력 (tail -f)
배포 및 확인:
# Pod 배포
kubectl apply -f ubuntu-emptydir.yaml
# container-a의 로그 확인 (container-b가 기록한 데이터)
kubectl logs -c container-a test-pod | head -n 5
# Pod 삭제 (emptyDir도 함께 삭제됨)
kubectl delete -f ubuntu-emptydir.yaml
출력 예시:
Sat Jan 14 12:00:00 UTC 2026
Sat Jan 14 12:00:03 UTC 2026
Sat Jan 14 12:00:06 UTC 2026
Sat Jan 14 12:00:09 UTC 2026
Sat Jan 14 12:00:12 UTC 2026
메모리 기반 emptyDir:
volumes:
- name: memory-volume
emptyDir:
medium: Memory # 메모리(tmpfs)에 저장
sizeLimit: 100Mi # 메모리 용량 제한 (필수, 노드 메모리 보호)
디스크 vs 메모리 emptyDir:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[디스크 기반 (기본)]
저장 위치: 노드 디스크
속도: 일반
용도: 일반 임시 파일
[메모리 기반 (medium: Memory)]
저장 위치: 노드 메모리 (tmpfs)
속도: 매우 빠름
용도: 고속 캐시, 민감 데이터 임시 처리
주의: 메모리 부족 시 Pod Eviction
- sizeLimit: emptyDir의 최대 용량 제한. 초과 시 파드가 강제 종료(Eviction)될 수 있음
- medium: Memory: emptyDir을 메모리(tmpfs)에 저장하는 옵션. 디스크보다 훨씬 빠르지만 노드 메모리 소모
- Pod Eviction: 리소스 부족 시 쿠버네티스가 파드를 강제로 종료시키는 것. 노드 안정성을 위한 메커니즘
- tail -f: 파일 끝을 실시간으로 추적하는 Linux 명령어. 로그 모니터링에 자주 사용
일반 임시 볼륨 (Generic Ephemeral Volume, 예시)
일반 임시 볼륨이란?
파드 매니페스트에 볼륨 요청을 직접 기술하고, 해당 파드와 동일한 수명을 지닌 볼륨을 동적으로 생성할 수 있음
emptyDir vs 일반 임시 볼륨:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[emptyDir]
저장 위치: 노드 디스크/메모리
용량: 노드 리소스에 의존
관리: 쿠버네티스 직접 관리
vs
[일반 임시 볼륨]
저장 위치: CSI 프로비저너가 제공하는 스토리지
용량: 동적 프로비저닝으로 할당
관리: CSI 드라이버 관리
특징:
├─ 클라우드 디스크 활용
├─ emptyDir보다 신뢰성 높음
└─ Pod 삭제 시 자동 삭제 (임시 볼륨)
- 일반 임시 볼륨 (Generic Ephemeral Volume): CSI 드라이버를 통해 동적 프로비저닝되는 임시 볼륨. emptyDir보다 유연
- ephemeral: 파드 매니페스트에서 일반 임시 볼륨을 정의할 때 사용하는 필드
- volumeClaimTemplate: 일반 임시 볼륨에서 PVC를 자동 생성하기 위한 템플릿. StatefulSet에서도 사용
일반 임시 볼륨 예제 (AWS EBS):
파일명: ubuntu-gve-aws.yaml
apiVersion: v1 # Kubernetes API 버전
kind: Pod # 리소스 타입 - Pod
metadata:
name: test-pod # Pod 이름
spec:
containers:
# 볼륨에 저장된 데이터를 읽는 컨테이너
- name: container-a # 컨테이너 이름
image: ubuntu:22.04 # Ubuntu 이미지
command: ["sh"] # 실행할 명령어
args:
- -euc # 옵션
- "tail -f /mnt/shared-file" # /mnt/shared-file 실시간 읽기
volumeMounts: # 볼륨 마운트
- mountPath: /mnt/ # 일반 임시 볼륨을 /mnt/에 마운트
name: test-volume # 볼륨 이름
# 볼륨에 타임스탬프를 기록하는 컨테이너
- name: container-b # 컨테이너 이름
image: ubuntu:22.04 # Ubuntu 이미지
command: ["sh"] # 실행할 명령어
args:
- -euc # 옵션
- | # YAML 멀티라인 문자열
for i in $(seq 1 10) ; do # 1부터 10까지 반복
date >> /mnt/shared-file # 타임스탬프를 /mnt/shared-file에 추가
sleep 3 # 3초 대기
done ; sleep infinity # 10번 실행 후 무한 대기
volumeMounts: # 볼륨 마운트
- mountPath: /mnt/ # 일반 임시 볼륨을 /mnt/에 마운트
name: test-volume # 동일한 볼륨 이름 (컨테이너 간 공유)
volumes: # Pod에서 사용할 볼륨 정의
- name: test-volume # 볼륨 이름
ephemeral: # 일반 임시 볼륨 사용
# 볼륨 요청으로 얻은 볼륨을 일반 임시 볼륨으로 사용
volumeClaimTemplate: # PVC 템플릿 (동적 프로비저닝)
metadata:
labels:
type: ephemeral # 레이블 (선택 사항)
spec:
accessModes: ["ReadWriteOnce"] # 접근 모드
storageClassName: gp3 # StorageClass 지정 (AWS EBS gp3)
resources:
requests:
storage: 500Mi # 500MB EBS 볼륨 자동 생성
동작 방식:
일반 임시 볼륨 생명주기:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[Pod 생성]
↓
[자동으로 PVC 생성]
이름: <pod-name>-<volume-name>
예: test-pod-test-volume
↓
[동적 프로비저닝]
AWS EBS 디스크 자동 생성 (500MB gp3)
↓
[PV 자동 생성 및 바인딩]
↓
[Pod에 마운트]
컨테이너 A, B가 동일 볼륨 공유
↓
[Pod 삭제]
↓
[자동으로 PVC, PV, EBS 삭제]
(reclaimPolicy: Delete)
배포 및 확인:
# Pod 배포 (자동으로 EBS 볼륨 생성됨)
kubectl apply -f ubuntu-gve-aws.yaml
# 자동 생성된 PVC 확인
kubectl get pvc
# container-a의 로그 확인
kubectl logs -c container-a test-pod | head -n 5
# Pod 삭제 (PVC와 EBS 볼륨도 자동 삭제됨)
kubectl delete -f ubuntu-gve-aws.yaml
볼륨 종류 비교
전체 볼륨 타입 정리:
| 볼륨 타입 | 수명 | 데이터 영속성 | 공유 범위 | 용도 |
|---|---|---|---|---|
| emptyDir (디스크) | Pod | 없음 | Pod 내부 | 임시 파일, 캐시 |
| emptyDir (메모리) | Pod | 없음 | Pod 내부 | 고속 캐시 |
| 일반 임시 볼륨 | Pod | 없음 | Pod 내부 | 임시 클라우드 디스크 |
| PersistentVolume | 독립적 | 있음 | 클러스터 전체 | 영구 데이터 (DB, 파일) |
| hostPath | 노드 | 있음 (단일 노드) | 단일 노드 | 노드 파일 접근 (개발/테스트) |
| ConfigMap | 독립적 | 있음 | 클러스터 전체 | 설정 파일 |
| Secret | 독립적 | 있음 | 클러스터 전체 | 민감 정보 |
선택 가이드:
볼륨 선택 가이드:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[질문 1] 데이터를 영구 보존해야 하나요?
│
├─ Yes → PersistentVolume 사용
│ (데이터베이스, 파일 스토리지)
│
└─ No → 질문 2로
[질문 2] 컨테이너 간 데이터 공유가 필요한가요?
│
├─ Yes → emptyDir 또는 일반 임시 볼륨
│ (로그 파일 공유, 임시 데이터 전달)
│
└─ No → 질문 3으로
[질문 3] 고속 성능이 필요한가요?
│
├─ Yes → emptyDir (medium: Memory)
│ (고속 캐시)
│
└─ No → emptyDir (디스크)
(일반 임시 파일)
[특수 용도]
설정 정보 → ConfigMap
비밀번호 → Secret
참고 자료
공식 문서:
- Volumes: https://kubernetes.io/docs/concepts/storage/volumes/
- Persistent Volumes: https://kubernetes.io/docs/concepts/storage/persistent-volumes/
- Storage Classes: https://kubernetes.io/docs/concepts/storage/storage-classes/
- ConfigMaps: https://kubernetes.io/docs/concepts/configuration/configmap/
- Secrets: https://kubernetes.io/docs/concepts/configuration/secret/
CSI 드라이버:
- AWS EBS CSI Driver: https://github.com/kubernetes-sigs/aws-ebs-csi-driver
- GCP PD CSI Driver: https://github.com/kubernetes-sigs/gcp-compute-persistent-disk-csi-driver
- Azure Disk CSI Driver: https://github.com/kubernetes-sigs/azuredisk-csi-driver
베스트 프랙티스:
- ConfigMap/Secret은 민감도에 따라 구분하여 사용
- Secret은 etcd 암호화 설정 권장
- PersistentVolume은 백업 정책 필수 수립
- emptyDir (메모리)는 sizeLimit 설정 필수 (노드 메모리 보호)
- 동적 프로비저닝 사용 권장 (수동 PV 관리 부담 감소)
- StorageClass는 환경별로 구분 (dev, staging, prod)